<?php
namespace GrouppaGroup;

require_once 'Settings.php';
require_once 'Controller.php';
require_once 'MessageFromApp.php';
require_once 'MessageFromServer.php';
require_once 'ApplicationTCPConnection.php';
require_once 'Db.php';

class GeneralServicesController extends Controller
{

    /**
     * receives a message from an application (override from controller)
     * @param  MessageFromApp    $message
     * @return MessageFromServer return message to the user
     */
    public function receiveMessage(MessageFromApp $message)
    {
        //CHECK: check which function is supposed to be called and call it

        if ($message->task) {
            switch ($message->task) {
                case 'login':
                    $this->login($message);
                    break;
                case 'createAccount':
                    $this->createAccount($message);
                    break;
                case 'resetPassword':
                    $this->resetPassword($message);
                    break;
                default:
                    self::unknownTask($message);
                    break;
            }
        }
    }

    /**
     * Creates an access token for specified user
     * stores it in database
     * the username and userId must match and exist
     * @param  integer   $userId
     * @param  string    $userName
     * @return string    access token
     * @throws Exception if user doesn't exist
     */
    public static function getAccessToken($userName)
    {
        //CHECK: generate access token for the user

        $validTo = strtotime('now') + USER_ACCESS_TOKEN_TIME;
        $accessToken = base64_encode(mhash(MHASH_ADLER32, $userName . $validTo));
        $db = Db::getInstance();

        $result = $db->sendQuery("UPDATE users SET access_token='$accessToken', access_token_valid_to=$validTo " .
            "WHERE user_name='$userName';");

        if ($db->getAffectedRows() == 0)
            throw new \Exception('GeneralServicesController::getAccessToken error: user :' .
                $userName . ' doesn\'t exist');

        return $accessToken;

    }

    /*
    ****************** Section: functions available to call for the applications
    */
    /**
     * tries to login a user
     * it will send back to client this json structure:
     * {"loggedIn":&lt;true if login succeeded&gt;,"accessToken":&lt;access token in case loggedIn is true&gt;}
     * @param  MessageFromApp $message a message with json object in serialized arguments:
     *                                 {username:"&lt;user name&gt;" , password:"&lt;password&gt;"}
     * @return boolean        true iff user logged in
     */
    public function login(MessageFromApp $message)
    {
        //CHECKED: check username and password and provide accessToken if they are correct

        if (!$message->serializedArguments || !is_a($message->serializedArguments,
            'stdClass')) {
            throw new \Exception('GeneralServices::login error: message didn\'t contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'email') || !property_exists($args, 'password')) {
            throw new \Exception('GeneralServices::login error: message parameters didn\'t contain password and email');
        }

        $email = $args->email;
        $password = $args->password;

        if (!$email || !$password) {
            $serializedArguments = '{"loggedIn":false,"reason":"email, password shouldn\'t be empty"}';

        } elseif (strlen($email) > 45 || strlen($password) > 45) {

            $serializedArguments = '{"loggedIn":false,"reason":"email, password shouldn\'t' .
                ' be longer than 45 characters"}';

        } else {

            $result = Db::getInstance()->sendQuery("SELECT id_user,user_name FROM users WHERE email='$email' AND password='$password';");

            $loggedIn = $result->num_rows > 0;
            $row = $result->fetch_array(MYSQLI_NUM);
            $userId = $row[0];
            $userName = $row[1];
            $result->free();
            $groupsArray = array();
            //Getting groups to which that user joined
            if ($loggedIn) {

                $result = Db::getInstance()->sendQuery('SELECT groups.group_name , NOT(group_managers.id_user IS NULL), groups.group_password FROM groups
                INNER JOIN usergroups ON usergroups.id_group = groups.id_group
                LEFT JOIN group_managers ON groups.id_group = group_managers.id_group AND group_managers.id_user = usergroups.id_user
                WHERE usergroups.id_user='.$userId.';');
                $nGroups = $result->num_rows;
                for ($i = 0; $i < $nGroups; $i++) {
                    $row = $result->fetch_row();
                    $groupsArray[] = array('groupName'=>$row[0],'isManager'=>($row[1]?true:false),
                    'pin'=>$row[2]);
                }
                $result->free();
            }

            $serializedArguments = json_encode(array(
                'loggedIn' => $loggedIn,
                'accessToken' => ($loggedIn ? self::getAccessToken($userName) : ''),
                'username' => $userName,
                'reason' => ($loggedIn ? '' : 'wrong password or email'),
                'groups' => $groupsArray,
                'pin' => $password));
        }

        $responseMessage = new MessageFromServer($serializedArguments, 'GeneralServices',
            $message->messageType, $message->id);

        ApplicationTCPConnection::sendMessage($responseMessage);

        return $loggedIn;
    }

    /**
     * tries to create a new account
     * sends back to client a message with this json object as serializedArguments:
     * {"created": &lt; true if create succeeded &gt; ,"accessToken": "&lt; access token in case created is true &gt;" }
     * @param  MessageFromApp $message a message with json object in serialized arguments:
     *                                 {"username":"&lt;user name&gt;" , "password":"&lt;password&gt;", "email":"&lt;email&gt;"}
     * @return boolean        true iff account created
     */
    public function createAccount($message)
    {
        //CHECKED: check username and password, create an account and provide accesstoken
        if (!$message->serializedArguments || !is_a($message->serializedArguments,
            'stdClass')) {
            throw new \Exception('GeneralServices::createAccount error: message didn\'t contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'username') || !property_exists($args, 'password') ||
            !property_exists($args, 'email')) {
            throw new \Exception('GeneralServices::createAccount error: message parameters didn\'t contain password or username or email');
        }

        $userName = $args->username;
        $password = $args->password;
        $email = $args->email;

        $serializedArguments;
        // arguments for returning message
        $created = false;
        //result - did we create an account?

        if (!$userName || !$password || !$email) {
            $serializedArguments =
                '{"created":false,"reason":"user name, password and email shouldn\'t be empty"}';

        } elseif (strlen($userName) > 45 || strlen($password) > 45 || strlen($email) > 45) {

            $serializedArguments =
                '{"created":false,"reason":"user name, password and email shouldn\'t' .
                ' be longer than 45 characters"}';

        } else {
            $db = Db::getInstance();
            $created = $db->sendInsertQuery("INSERT INTO users SET user_name='$userName', password='$password', email='$email';",
                'users');

            $serializedArguments;
            if ($created) {
                $serializedArguments = '{"created":true,"accessToken":"' . self::getAccessToken($userName) .
                    '","username":"' . $userName . '"}';
            } else {
                $serializedArguments = '{"created":false,"reason":"user name already taken"}';
            }
        }
        $responseMessage = new MessageFromServer($serializedArguments, 'GeneralServices',
            $message->messageType, $message->id);

        ApplicationTCPConnection::sendMessage($responseMessage);

        return $created;
    }

    /**
     * sends password of a username to a specified email. Checks if email
     * was earlier in user profile
     * sends back to client a message with this json object as serializedArguments:
     * {"emailSent": &lt; true iff email sent &gt; ,"reason": "&lt; reason why email wasn't sent (if necessary) &gt;" }
     * @param MessageFromApp $message a message with json object in serialized arguments:
     *                                {username:"&lt;user name&gt;" , password:"&lt;password&gt;"}
     */
    public function resetPassword(MessageFromApp $message)
    {
        if (!$message->serializedArguments || !is_a($message->serializedArguments,
            'stdClass')) {
            throw new \Exception('GeneralServices::resetPassword error: message didn\'t contain proper json object');
        }
        $args = $message->serializedArguments;
        if (!property_exists($args, 'email')) {
            throw new \Exception('GeneralServices::resetPassword error: message parameters didn\'t contain email');
        }

        //CHECK: retrieve original password and user name for $email
        $email = $args->email;
        $db = Db::getInstance();
        $result = $db->sendQuery('SELECT password,user_name FROM users WHERE email="' .
            $email . '";');

        if ($result->num_rows == 0) { //nothing returned
            $responseMessage = new MessageFromServer('{"emailSent":false,"reason":"Email address doesn\'t exist"}',
                'GeneralServices', $message->messageType, $message->id);
            ApplicationTCPConnection::sendMessage($responseMessage);

            return false;
        }

        $row = $result->fetch_array(MYSQLI_NUM);
        // password = row[0], user_name = row[1]

        //CHECK: send original password to email if check succeeded
        $this->sendEmailPassword($row[1], $email, $row[0]);

        $responseMessage = new MessageFromServer('{"emailSent":true}', 'GeneralServices',
            $message->messageType, $message->id);
        ApplicationTCPConnection::sendMessage($responseMessage);

        return true;
    }

    /**
     * Sends email to user with password remind
     * @param string $userName username to which send email
     * @param string $email    email to which send password
     * @param string $password password of the user
     */
    private function sendEmailPassword($userName, $email, $password)
    {
        $title = 'Grouppa password reminder';
        $message = <<< EOD
<html>
<body>
    <h2>Grouppa Password Reminder</h2>
    <h4>Hello $userName,</h4>
    Recently you asked us to remind you your password<br/>
    Here is your password: $password
</body>
</html>
EOD;

        $headers = "From: info@kxstilburg.eu\r\nMIME-Version: 1.0\r\nContent-type: text/html; charset=utf-8\r\nX-Mailer: PHP/" .
            phpversion();

        if (!mail($email, $title, $message, $headers)) {
            throw new \Exception('GeneralServicesController::sendEmailPassword: couldn\'t send email, check server mail configuration');
        }

    }

}
